#ifndef _JERASURE_H
#define _JERASURE_H

#include <stdint.h>
#include <vector>
#include <string>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>

#define talloc(type, num) (type *) malloc(sizeof(type)*(num))

using namespace std;

//arguments passed to encode pthreads 
typedef struct{
	class JER_Slices *slices;
	class JER_Region *region;
	int id;
	int thread_count;
	string *method;
	class JER_Matrix * jm;
	vector <int> * jm_super_row_ids;
	vector <int> * dest_disk_ids;
	int * dm_ids;
} pt_dotprod_args;

class JER_Schedule {
	public:
		vector < vector <unsigned int> > Elts;
};

class JER_Gen_T {
	public:
		JER_Gen_T();
		~JER_Gen_T();

		int N;
		int K;
		int WPD;

		class JER_Matrix *M;

		bool Systematic;
		bool PDrive;

		/* Cache containing encode and decode schedules, 
		   key is a string of 0's and 1's representing which drives failed */
		map <string, JER_Schedule * > Schedules;

		/* Determines if M is an MDS matrix. ( If M is invertible after 
		   dropping all combinations of n - k rows. ) */
		int Am_I_MDS();
		/* Creates a single schedule of M to use for encoding. 
		   Stores schedule in Schedules. */
		int Create_Encode_Schedule(int smart);
		/* Creates decoding schedules for all possible combinations of 1 or 2 
		   disk failures. Only for m = 2. */
		int Create_R6_Schedules(int smart);
		/* Clears all entries in Schedules map. */
		void Delete_Schedules();

		/* Returns a schedule for decoding given the set of failed drives. */
		JER_Schedule * Create_Single_Decode_Schedule(vector <int> & erased, int smart);

		/* Converts object from when M is a matrix with W > 1 to a bitmatrix 
		   with W == 1 and WPD > 1. Also vice versa. */
		JER_Gen_T *Genmatrix_To_Genbitmatrix();
		JER_Gen_T *Genbitmatrix_To_Genmatrix();
		int Genbitmatrix_To_Genmatrix( JER_Gen_T &gen );
		int Genmatrix_To_Genbitmatrix( JER_Gen_T &bgen );
		
		bool rs_r6;
	private:
		/* Used in Am_I_MDS(). */
		int next_combination(vector <int> & n, vector <int> & r);
};

class JER_Region {
	public:
		int drive;
		int start;
		int size;
};

class JER_Slices{
	public:
		int N;
		int K;
		int PacketSize;
		// Must be a multiple of WPD
		int PacketsPerSlice;
		JER_Gen_T *G;
		int DataOnly;                 /* This is really only relevant for vertical codes */
		vector <unsigned char *> Ptrs; /* Ptr is NULL if the "disk" is absent */
		vector <int> States;           /* States is 0 for up, 1 for down, 2 for unusable */
		vector <JER_Region *> Pfs;     /* Partial failures */

		string MultiThreadMethod; //disks, packet_rows, packet_cols, packets, best
		int NumberOfCores; //# of cores available. encode and decode will try to have this many threads working simultaneously

		unsigned long long XORs;                           /* Stats */
		unsigned long long GF_Mults;
		unsigned long long Memcpys;

		/* This constructor lets the user set ptrs.  If "data_only",
			 then it assumes that the specified coding drives exist, but
			 they are in a failed state.  In a vertical code, it assumes
			 that the coding areas are properly allocated and that the data
			 is in the correct place, but the coding areas are not initialized.
			 That may be yucky. */

		JER_Slices(int n, int k, int ps, int pps, JER_Gen_T *g,vector <unsigned char *> ptrs, int data_only);

		/* This constructor creates new pointers from ptrs, and
			 performs encoding at the same time */

		JER_Slices(int n, int k, int ps, int pps, JER_Gen_T *g,
				vector <unsigned char *> ptrs);

		/* This constructor allocates and creates empty slices */

		JER_Slices(int n, int k, int ps, int pps, JER_Gen_T *g);

		void Add_Partial_Failure(int drive, int start, int size);
		void Add_Partial_Failure(JER_Region &r);
		void Add_Drive_Failure(int drive);

		void Remove_Drive(int drive);
		void Remove_Drive_And_Re_Encode(int drive);

		int Update_Region(int drive, int start, int size, unsigned char *new_data);
		int Update_Region(JER_Region &r, unsigned char *new_data);

		int Encode();//calls the appropriate private encode function
		int Decode();
		int Decode_Schedule_Lazy(int smart);

		//This is similar to the original Dotprod routine, but now you pass multiple src/dest ids. If NumberOfCores == 1, this will call dotprod_disk for each disk. Otherwise, it creates dotprod_threads which call Dotprod_Disk,Row,Col, or Packet.
		int Dotprod(JER_Matrix *jm, vector <int> & jm_super_row_ids, vector <int> & dest_disk_ids, int *dm_ids, JER_Region *region = NULL );

		int Recover_Partial_Failures();

		JER_Matrix * Make_Decoding_Matrix(int *dm_ids);
		void Do_Parity(unsigned char * parity_ptr);

	private:
		//These private dotprod routines do not do error checking, because their caller (Dotprod) does.
		void dotprod_disk(JER_Matrix *jm, int jm_super_row_id, int dest_disk_id, int *dm_ids, JER_Region *region);
		void dotprod_row(JER_Matrix *jm, int jm_row_id, int dest_row_id, int *dm_ids, JER_Region *region);
		void dotprod_col(JER_Matrix *jm, vector <int> & jm_super_row_ids, vector <int> & dest_disk_ids, int dest_col_id, int *dm_ids, JER_Region *region );

		void dotprod_packet(JER_Matrix *jm, int jm_row_id, int dest_packet_id, int *dm_ids, JER_Region *region );

		vector <unsigned char *> *set_up_ptrs_for_scheduled_decoding();
		void do_scheduled_operations(vector <unsigned char *> &ptrs, JER_Schedule *sched);
		int encode_rs_r6();
		int encode_schedule(JER_Schedule *sched);
		int decode_schedule(JER_Schedule *sched);
		int encode_matrix();
		int decode_matrix();

		//functions for multi-threading:
		//must be static to be run by a pthread
		static void *dotprod_thread(void *v);

};

class JER_Matrix {

	public:
		vector <uint64_t> Elts;
		int R;
		int C;
		int W;

		/* Creates an empty matrix. Second constructor set values of
		   r, c, and w, as well as resizing Elts. */
		JER_Matrix();
		JER_Matrix( int r, int c, int w );

		/* Sets values of previously allocated matrix to all 0s or 
		   an identity matrix. */
		int Create_Empty( int r, int c, int w );
		int Create_Identity( int r, int c, int w );

		/* Sets the r,c element of matrix to val.
		   Gets the value of r,c element of matrix. */
		void Set( int r, int c, uint64_t val );
		uint64_t Get( int r, int c );

		/* Prints the contents of the matrix to stdout. 
		   Returns a string of the contents of the matrix. */
		void Print(int WPD = 1);
		string String(int WPD = 1);

		/* Overwrites values of matrix with values of m.
		   XORs values of matrix with values of m. */
		int Copy( JER_Matrix *m );
		int Add( JER_Matrix *m );

		/* Appends the rth row or cth column of m to the matrix. */
		int Append_Row( JER_Matrix *m, int r );
		int Append_Col( JER_Matrix *m, int c );

		/* Deletes the rth row or cth column of the matrix. Resizes matrix. */
		void Delete_Row( int r );
		void Delete_Col( int c );

		/* Deletes nr rows or nc columns starting with starting_r/c. */
		void Delete_Rows( int starting_r, int nr );
		void Delete_Cols( int starting_c, int nc );

		/* Copies values from m into matrix starting at element dest_r,dest_c.
		   If src_r, src_c specified, only copies a submatrix of m. */
		int Copy_Panel( JER_Matrix *m, int dest_r, int dest_c );
		int Copy_Panel( JER_Matrix *m, int src_r, int src_c, int dest_r, 
				int dest_c, int nr, int nc );

		/* XORs values from m into matrix starting at element dest_r,dest_c.
		   If src_r, src_c specified, only copies a submatrix of m. */
		int Add_Panel( JER_Matrix *m, int dest_r, int dest_c );
		int Add_Panel( JER_Matrix *m, int src_r, int src_c, int dest_r, 
				int dest_c, int nr, int nc );

		/* Cycles the columns/rows of the matrix by cols/rows. */
		void Horizontal_Rotate( int cols );
		void Vertical_Rotate( int rows );

		/* Swaps the values in row r1 with those in row r2. Similar with cols. */
		void Swap_Rows( int r1, int r2 );
		void Swap_Cols( int c1, int c2 );

		/* Row r1 += row r2. Similar for col. */
		void Row_PlusEquals( int r1, int r2 );
		void Col_PlusEquals( int c1, int c2 );

		/* Row r1 += (row r2 * prod ). Similar for col. */
		void Row_PlusEquals_Prod( int r1, int r2, int prod );
		void Col_PlusEquals_Prod( int c1, int c2, int prod );

		/* Row r1 *= prod. Similar for col. */
		void Row_TimesEquals( int r1, int prod );
		void Col_TimesEquals( int c1, int prod );

		JER_Schedule *To_Schedule_CSHR(int WPD);
		JER_Schedule *To_Schedule_Dumb(int WPD);

		/* Converts a matrix to bitmatrix */
		JER_Matrix *Matrix_To_Bitmatrix(); 
		JER_Matrix *Bitmatrix_To_Matrix( int WPD );
		int Matrix_To_Bitmatrix( JER_Matrix &jbm ); 
		int Bitmatrix_To_Matrix( JER_Matrix &jm, int WPD );

	private:
		int Resize_Elts();
		int copy_panel( JER_Matrix *m, int src_r, int src_c, int dest_r, int dest_c, int nr, int nc );
};

/* Adds m1 to m2 and returns it or stores in sum if specified. */
JER_Matrix* Sum( JER_Matrix *m1, JER_Matrix *m2 );
JER_Matrix Sum( JER_Matrix &m1, JER_Matrix &m2 );
int Sum(JER_Matrix *m1, JER_Matrix *m2, JER_Matrix *sum);
int Sum(JER_Matrix &m1, JER_Matrix &m2, JER_Matrix &sum);

/* Multiplies m1 to m2 and returns it or stores in prod if specified. */
JER_Matrix *Prod( JER_Matrix *m1, JER_Matrix *m2 );
JER_Matrix Prod( JER_Matrix &m1, JER_Matrix &m2 );
int Prod(JER_Matrix *m1, JER_Matrix *m2, JER_Matrix *prod);
int Prod(JER_Matrix &m1, JER_Matrix &m2, JER_Matrix &prod);

/* Takes inverse of m and return it or stores in inv if specified. */
JER_Matrix *Inverse( JER_Matrix *m );
JER_Matrix Inverse( JER_Matrix &m );
int Inverse( JER_Matrix *m, JER_Matrix *inv );
int Inverse( JER_Matrix &m, JER_Matrix &inv );

#endif
